#define LOAD		lw		// 从内存加载到CPU
#define STORE		sw	    // 从CPU存到内存
#define SIZE_REG	4

# 将所有通用（GP）寄存器保存到上下文。
# struct context *base = &ctx_task;
# base->ra = ra;
# ......
# 需要保存的这些 GP 寄存器不包含 gp 和 tp，因为它们既不是调用者保存也不是被调用者保存。
# 这两个寄存器通常用于特殊用途。例如，在 RVOS 中，tp（又名“线程指针”）用于存放 hartid，
# 这是一个全局值，并不会在上下文切换期间改变。
.macro reg_save base
	STORE ra,   0*SIZE_REG(\base)
	STORE sp,   1*SIZE_REG(\base)
	STORE t0,   4*SIZE_REG(\base)
	STORE t1,   5*SIZE_REG(\base)
	STORE t2,   6*SIZE_REG(\base)
	STORE s0,   7*SIZE_REG(\base)
	STORE s1,   8*SIZE_REG(\base)
	STORE a0,   9*SIZE_REG(\base)
	STORE a1,  10*SIZE_REG(\base)
	STORE a2,  11*SIZE_REG(\base)
	STORE a3,  12*SIZE_REG(\base)
	STORE a4,  13*SIZE_REG(\base)
	STORE a5,  14*SIZE_REG(\base)
	STORE a6,  15*SIZE_REG(\base)
	STORE a7,  16*SIZE_REG(\base)
	STORE s2,  17*SIZE_REG(\base)
	STORE s3,  18*SIZE_REG(\base)
	STORE s4,  19*SIZE_REG(\base)
	STORE s5,  20*SIZE_REG(\base)
	STORE s6,  21*SIZE_REG(\base)
	STORE s7,  22*SIZE_REG(\base)
	STORE s8,  23*SIZE_REG(\base)
	STORE s9,  24*SIZE_REG(\base)
	STORE s10, 25*SIZE_REG(\base)
	STORE s11, 26*SIZE_REG(\base)
	STORE t3,  27*SIZE_REG(\base)
	STORE t4,  28*SIZE_REG(\base)
	STORE t5,  29*SIZE_REG(\base)
	# 这里不保存 t6，因为我们将它用作 base，需要在 reg_save 外的额外步骤中保存 t6
.endm

# 从上下文中恢复所有通用（GP）寄存器，gp 和 tp 除外。
# struct context *base = &ctx_task;
# ra = base->ra;
# ......
.macro reg_restore base
	LOAD ra,   0*SIZE_REG(\base)
	LOAD sp,   1*SIZE_REG(\base)
	LOAD t0,   4*SIZE_REG(\base)
	LOAD t1,   5*SIZE_REG(\base)
	LOAD t2,   6*SIZE_REG(\base)
	LOAD s0,   7*SIZE_REG(\base)
	LOAD s1,   8*SIZE_REG(\base)
	LOAD a0,   9*SIZE_REG(\base)
	LOAD a1,  10*SIZE_REG(\base)
	LOAD a2,  11*SIZE_REG(\base)
	LOAD a3,  12*SIZE_REG(\base)
	LOAD a4,  13*SIZE_REG(\base)
	LOAD a5,  14*SIZE_REG(\base)
	LOAD a6,  15*SIZE_REG(\base)
	LOAD a7,  16*SIZE_REG(\base)
	LOAD s2,  17*SIZE_REG(\base)
	LOAD s3,  18*SIZE_REG(\base)
	LOAD s4,  19*SIZE_REG(\base)
	LOAD s5,  20*SIZE_REG(\base)
	LOAD s6,  21*SIZE_REG(\base)
	LOAD s7,  22*SIZE_REG(\base)
	LOAD s8,  23*SIZE_REG(\base)
	LOAD s9,  24*SIZE_REG(\base)
	LOAD s10, 25*SIZE_REG(\base)
	LOAD s11, 26*SIZE_REG(\base)
	LOAD t3,  27*SIZE_REG(\base)
	LOAD t4,  28*SIZE_REG(\base)
	LOAD t5,  29*SIZE_REG(\base)
	LOAD t6,  30*SIZE_REG(\base)
.endm

# 关于保存/恢复的注意事项：
# - 用 mscratch 保存“当前任务的上下文指针”
# - 使用 t6 作为 reg_save/reg_restore 的“基址”，因为它是编号最大的寄存器（x31），
#   在加载过程中不会被覆盖。
#   注意：CSR（mscratch）不能作为“基址”，因为加载/存储指令只接受通用寄存器。

.text

# 机器模式下发生的中断与异常都会来到这里。
.globl trap_vector
# 陷阱向量（trap vector）的基地址必须始终按 4 字节边界对齐。
# 因为函数入口按 4 字节对齐，低 2 位为 0，所以 MODE=0 被同时写入
.balign 4
trap_vector:
	# 保存上下文（各寄存器）。
	csrrw	t6, mscratch, t6	# 交换 t6 与 mscratch
	reg_save t6

	# 保存真正的 t6 寄存器（我们刚把它交换进了 mscratch）
	mv	t5, t6					# t5 指向当前任务的上下文
	csrr	t6, mscratch		# 从 mscratch 取回 t6
	STORE	t6, 30*SIZE_REG(t5)	# 以 t5 为基址保存 t6

	# 把（上下文）指针恢复写回到 mscratch
	csrw	mscratch, t5

	# 调用 trap.c 中的 C 语言异常处理函数
	csrr	a0, mepc	# 第1个实参
	csrr	a1, mcause	# 第2个实参
	call	trap_handler

	# trap_handler 会通过 a0 返回 return_pc
	csrw	mepc, a0

	# 恢复上下文（各寄存器）。
	csrr	t6, mscratch
	reg_restore t6

	# 返回到触发异常/中断之前正在执行的代码处。
	mret

# void switch_to(struct context *next);
# a0：指向下一个任务上下文的指针，进入switch_to时a0 = next
.globl switch_to		# 把符号 switch_to 标记为全局可见
.balign 4
switch_to:
	csrrw	t6, mscratch, t6	# 交换 t6 与 mscratch，
	beqz	t6, 1f			# 注意：第一次调用 switch_to() 时，
	                              # mscratch 在 sched_init() 中被初始化为 0，
					              # 这会使 t6 为 0，表示第一次调度前没有前一个任务可存
					              # 从而跳转到1：，跳过后面的保存上下文的步骤
	reg_save t6			# 保存上一个任务的上下文

	# 保存真正的 t6 寄存器（我们刚把它交换进 mscratch）
	mv	t5, t6			# t5 指向当前任务的上下文
	csrr	t6, mscratch		# 从 mscratch 中读回 t6
	STORE	t6, 30*SIZE_REG(t5)	# 以 t5 为 base 保存 t6

1:
	# 将 mscratch 切换为指向下一个任务的上下文
	csrw	mscratch, a0

	# 恢复所有 GP 寄存器
	# 使用 t6 指向新任务的上下文
	mv	t6, a0
	reg_restore t6

	# 执行实际的上下文切换。
	ret

.end

